#include "trap.h"

#include "arm.h"
#include "sysregs.h"
#include "mmu.h"
#include "syscall.h"
#include "peripherals/irq.h"

#include "uart.h"
#include "console.h"
#include "clock.h"
#include "timer.h"
#include "proc.h"
#include "sd.h"

void
irq_init()
{
    clock_init();
    put32(ENABLE_IRQS_1, AUX_INT);
    put32(ENABLE_IRQS_2, VC_ARASANSDIO_INT);
    put32(GPU_INT_ROUTE, GPU_IRQ2CORE(0));
}

static inline void print_trapframe(struct proc *p) {
    struct trapframe *t = p->tf;

    cprintf("====trapframe of pid=%d=====\n", p->pid);
    for (int i = 0; i < 31; i++) {
        cprintf("- X%d = 0x%llx\n", i, t->x[i]);
    }
    cprintf("- SP    = 0x%llx\n", t->stack);
    cprintf("- ELR   = 0x%llx\n", t->pc);
    cprintf("- SPSR  = 0x%llx\n", t->pstate);
    cprintf("- TPIDR = 0x%llx\n", t->tpidr);
    cprintf("- Q0    = 0x%llx 0x%llx\n", t->q0[0], t->q0[1]);
    cprintf("============================\n");
}

void report_and_exit() {
    struct proc *p = thisproc();
    print_trapframe(p);
    exit();
}

static void
interrupt(struct trapframe *tf, uint64_t esr)
{
    struct proc *p = thisproc();

    int src = get32(IRQ_SRC_CORE(arm_cpuid()));
    if (src & IRQ_CNTPNSIRQ) {
        timer_reset();
        timer();
    } else if (src & IRQ_TIMER) {
        clock_reset();
        clock();
    } else if (src & IRQ_GPU) {
        int p1 = get32(IRQ_PENDING_1), p2 = get32(IRQ_PENDING_2);
        if (p1 & AUX_INT) {
            uart_intr();
        } else if (p2 & VC_ARASANSDIO_INT) {
            sd_intr();
        } else {
            cprintf("unexpected gpu intr p1 %x, p2 %x, sd %d", p1, p2, p2 & VC_ARASANSDIO_INT);
            goto report;
        }
    } else {
        cprintf("unexpected interrupt at cpu %d", arm_cpuid());
        goto report;
    }

    return;

report:
    if (p) {
        cprintf("cpu %d: pid=%d, unexpected interrupt, exited. ESR=0x%p\n",
            arm_cpuid(), p->pid, esr);
        report_and_exit();
    } else
        panic("cpu %d: unexpected interrupt. ESR=0x%p, ELR=0x%p", arm_cpuid(), esr, arm_get_elr());
}

void
trap(struct trapframe *tf)
{
    struct proc *p = thisproc();
    uint64_t esr = arm_get_esr();
    int ec = esr >> EC_SHIFT, iss = esr & ISS_MASK;
    arm_set_esr(0);  /* Clear esr. */

    switch (ec) {
    case EC_UNKNOWN:
        interrupt(tf, esr);
        break;

    case EC_SVC64:
        if (iss == 0) {
            /* Jump to syscall to handle the system call from user process */
            int sysnum;
            int64_t sysret;
            sysnum = tf->x[8];

            if ((sysret = syscall(sysnum)) < 0)
                cprintf("trap: warning: syscall %d (\"%s\") returns %lld.\n",
                    sysnum, get_syscall_name(sysnum), sysret);

            tf->x[0] = sysret;  // feedback to user program

            arm_set_esr(0);  /* Clear esr. */
        break;

        } else {
            panic("trap: unexpected svc: %lld (0x%llx).", iss, iss);
        }
        break;

    default:
        if (p) {
            cprintf("cpu %d: pid=%d, unexpected irq, exited. ESR=0x%p\n",
                arm_cpuid(), p->pid, esr);
            report_and_exit();
        } else
            panic("trap: unexpected irq. ESR=0x%p, ELR=0x%p", esr, arm_get_elr());
    }
}

void
irq_error(uint64_t type)
{
    uint64_t esr = arm_get_esr();
    struct proc *p = thisproc();

    if (p) {
        cprintf("cpu %d: pid=%d, unexpected irq, exited. ESR=0x%p\n",
            arm_cpuid(), p->pid, esr);
        report_and_exit();
    } else
        panic("irq_error: irq of type %d unimplemented. ESR=0x%p", type, esr);
}
